#include "egl_core.h"
#include "dfx/error/av_codec_sample_error.h"
#include "plugin_render.h"
#include <cstdint>

#undef LOG_TAG
#define LOG_TAG "EGLCORE"

namespace {
/**
 * Vertex shader.
 */
const char VERTEX_SHADER[] = "#version 300 es\n"
                             "layout(location = 0) in vec4 a_position;\n"
                             "layout(location = 1) in vec4 a_color;   \n"
                             "out vec4 v_color;                       \n"
                             "void main()                             \n"
                             "{                                       \n"
                             "   gl_Position = a_position;            \n"
                             "   v_color = a_color;                   \n"
                             "}                                       \n";

/**
 * Fragment shader.
 */
const char FRAGMENT_SHADER[] = "#version 300 es\n"
                               "precision mediump float;                  \n"
                               "in vec4 v_color;                          \n"
                               "out vec4 fragColor;                       \n"
                               "void main()                               \n"
                               "{                                         \n"
                               "   fragColor = v_color;                   \n"
                               "}                                         \n";

/**
 * Fifty percent.
 */
const float FIFTY_PERCENT = 0.5;

/**
 * Egl red size default.
 */
const int EGL_RED_SIZE_DEFAULT = 8;

/**
 * Egl green size default.
 */
const int EGL_GREEN_SIZE_DEFAULT = 8;

/**
 * Egl blue size default.
 */
const int EGL_BLUE_SIZE_DEFAULT = 8;

/**
 * Egl alpha size default.
 */
const int EGL_ALPHA_SIZE_DEFAULT = 8;

/**
 * Config attribute list.
 */
const EGLint ATTRIB_LIST[] = {
    // Key,value.
    EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
    EGL_RED_SIZE, EGL_RED_SIZE_DEFAULT,
    EGL_GREEN_SIZE, EGL_GREEN_SIZE_DEFAULT,
    EGL_BLUE_SIZE, EGL_BLUE_SIZE_DEFAULT,
    EGL_ALPHA_SIZE, EGL_ALPHA_SIZE_DEFAULT,
    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
    // End.
    EGL_NONE};

/**
 * Context attributes.
 */
const EGLint CONTEXT_ATTRIBS[] = {
    EGL_CONTEXT_CLIENT_VERSION, 2,
    EGL_NONE};
} // namespace
int32_t EGLCore::EglContextInit(void* window)
{
    AVCODEC_SAMPLE_LOGI("EglContextInit execute");
    eglWindow_ = static_cast<EGLNativeWindowType>(window);

    // Init display.
    eglDisplay_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    CHECK_AND_RETURN_RET_LOG(eglDisplay_ != EGL_NO_DISPLAY, GL_FALSE, "eglGetDisplay: unable to get EGL display");

    EGLint majorVersion;
    EGLint minorVersion;
    CHECK_AND_RETURN_RET_LOG(eglInitialize(eglDisplay_, &majorVersion, &minorVersion),
        GL_FALSE, "eglInitialize: unable to get initialize EGL display");

    // Select configuration.
    const EGLint maxConfigSize = 1;
    EGLint numConfigs;
    CHECK_AND_RETURN_RET_LOG(eglChooseConfig(eglDisplay_, ATTRIB_LIST, &eglConfig_, maxConfigSize, &numConfigs),
        GL_FALSE, "eglChooseConfig: unable to choose configs");
    CHECK_AND_RETURN_RET_LOG(eglWindow_ != nullptr, GL_FALSE, "eglWindow_ is null");
    eglSurface_ = eglCreateWindowSurface(eglDisplay_, eglConfig_, eglWindow_, NULL);
    CHECK_AND_RETURN_RET_LOG(eglSurface_ != nullptr, GL_FALSE, "eglCreateWindowSurface: unable to create surface");
    // Create context.
    eglContext_ = eglCreateContext(eglDisplay_, eglConfig_, EGL_NO_CONTEXT, CONTEXT_ATTRIBS);
    CHECK_AND_RETURN_RET_LOG(eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_),
        GL_FALSE, "eglMakeCurrent failed");
    // Create program.
    program_ = CreateProgram(VERTEX_SHADER, FRAGMENT_SHADER);
    CHECK_AND_RETURN_RET_LOG(program_ == AVCODEC_SAMPLE_ERR_OK, 
        GL_FALSE, "CreateProgram: unable to create program");
    AVCODEC_SAMPLE_LOGI("EglContextInit success.");
    return GL_TRUE;
}

GLuint EGLCore::CreateProgram(const char* vertexShader, const char* fragShader)
{
    CHECK_AND_RETURN_RET_LOG(vertexShader != nullptr && fragShader != nullptr, 
        GL_FALSE, "createProgram: vertexShader or fragShader is null");
    GLuint vertex = LoadShader(GL_VERTEX_SHADER, vertexShader);
    CHECK_AND_RETURN_RET_LOG(vertex != GL_TRUE, GL_FALSE, "createProgram vertex error");
    GLuint fragment = LoadShader(GL_FRAGMENT_SHADER, fragShader);
    CHECK_AND_RETURN_RET_LOG(fragment != GL_TRUE, GL_FALSE, "createProgram fragment error");
    GLuint program = glCreateProgram();
    if (program == GL_FALSE) {
        AVCODEC_SAMPLE_LOGE("createProgram program error.");
        glDeleteShader(vertex);
        glDeleteShader(fragment);
        return GL_FALSE;
    }
    // The gl function has no return value.
    glAttachShader(program, vertex);
    glAttachShader(program, fragment);
    glLinkProgram(program);
    GLint linked;
    glGetProgramiv(program, GL_LINK_STATUS, &linked);
    if (linked != GL_FALSE) {
        glDeleteShader(vertex);
        glDeleteShader(fragment);
        return program;
    }
    AVCODEC_SAMPLE_LOGE("createProgram linked error.");
    GLint infoLen = 0;
    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
    if (infoLen > 1) {
        char* infoLog = (char*)malloc(sizeof(char) * (infoLen + 1));
        memset(infoLog, 0, infoLen + 1);
        glGetProgramInfoLog(program, infoLen, nullptr, infoLog);
        AVCODEC_SAMPLE_LOGE("glLinkProgram error = %s", infoLog);
        free(infoLog);
        infoLog = nullptr;
    }
    glDeleteShader(vertex);
    glDeleteShader(fragment);
    glDeleteProgram(program);
    return GL_FALSE;
}

GLuint EGLCore::LoadShader(GLenum type, const char* shaderSrc)
{
    CHECK_AND_RETURN_RET_LOG(type > 0 && shaderSrc != nullptr, 
        GL_FALSE, "glCreateShader type or shaderSrc error");
    GLuint shader = glCreateShader(type);
    CHECK_AND_RETURN_RET_LOG(shader != 0, 
        GL_FALSE, "glCreateShader unable to load shader");
    // The gl function has no return value.
    glShaderSource(shader, 1, &shaderSrc, nullptr);
    glCompileShader(shader);
    GLint compiled;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    CHECK_AND_RETURN_RET_LOG(compiled == GL_FALSE, shader, "get GL_COMPILE_STATUS failed");
    GLint infoLen = 0;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
    if (infoLen <= 1) {
        glDeleteShader(shader);
        return GL_FALSE;
    }
    char* infoLog = (char*)malloc(sizeof(char) * (infoLen + 1));
    if (infoLog != nullptr) {
        memset(infoLog, 0, infoLen + 1);
        glGetShaderInfoLog(shader, infoLen, nullptr, infoLog);
        AVCODEC_SAMPLE_LOGE("glLinkProgram error = %s", infoLog);
        free(infoLog);
        infoLog = nullptr;
    }
    glDeleteShader(shader);
    return GL_FALSE;
}

void EGLCore::UpdateSize(int width, int height)
{
    width_ = width;
    height_ = height;
    if (width_ > 0) {
        widthPercent_ = FIFTY_PERCENT * height_ / width_;
    }
}

void EGLCore::Release()
{
    if ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (!eglDestroySurface(eglDisplay_, eglSurface_))) {
        AVCODEC_SAMPLE_LOGE("Release eglDestroySurface failed");
    }

    if ((eglDisplay_ == nullptr) || (eglContext_ == nullptr) || (!eglDestroyContext(eglDisplay_, eglContext_))) {
        AVCODEC_SAMPLE_LOGE("Release eglDestroyContext failed");
    }

    if ((eglDisplay_ == nullptr) || (!eglTerminate(eglDisplay_))) {
        AVCODEC_SAMPLE_LOGE("Release eglTerminate failed");
    }
}
